using System.Collections.Generic;
using System.Linq;
using Ewk.MachineLearning.Classification.Interfaces;

namespace Ewk.MachineLearning.Classification.Common
{
    public static class LinkedEntityExtensions
    {
        public static void LinkToFeature<TClassification, TFeature, TLink>
            (this TClassification entity, TFeature feature)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            LinkFeatureToEntity<TClassification, TFeature, TLink>(entity, feature);
        }

        public static void LinkToFeatures<TClassification, TFeature, TLink>
            (this TClassification entity, IEnumerable<TFeature> features)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            if (features == null) return;

            foreach (var feature in features)
            {
                entity.LinkToFeature<TClassification, TFeature, TLink>(feature);
            }
        }

        public static void LinkToFeature<TClassification, TFeature, TLink>
            (this IEnumerable<TClassification> entities, TFeature feature)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            feature.LinkToEntities<TClassification, TFeature, TLink>(entities);
        }

        public static void LinkToFeatures<TClassification, TFeature, TLink>
            (this IEnumerable<TClassification> entities, IEnumerable<TFeature> features)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            LinkAllFeaturesToAllEntities<TClassification, TFeature, TLink>(entities, features.ToList());
        }

        public static void LinkToEntity<TClassification, TFeature, TLink>
            (this TFeature feature, TClassification entity)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            LinkFeatureToEntity<TClassification, TFeature, TLink>(entity, feature);
        }

        public static void LinkToEntities<TClassification, TFeature, TLink>
            (this TFeature feature, IEnumerable<TClassification> entities)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            if (entities == null) return;

            foreach (var entity in entities)
            {
                feature.LinkToEntity<TClassification, TFeature, TLink>(entity);
            }
        }

        public static void LinkToEntity<TClassification, TFeature, TLink>
            (this IEnumerable<TFeature> features, TClassification entity)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            entity.LinkToFeatures<TClassification, TFeature, TLink>(features);
        }

        public static void LinkToEntities<TClassification, TFeature, TLink>
            (this IEnumerable<TFeature> features, IEnumerable<TClassification> entities)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            LinkAllFeaturesToAllEntities<TClassification, TFeature, TLink>(entities, features.ToList());
        }

        private static void LinkFeatureToEntity<TClassification, TFeature, TLink>
            (TClassification entity, TFeature feature)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            var link = entity.Links.FindByFeature(feature) ??
                       CreateNewLink<TClassification, TFeature, TLink>(entity, feature);

            link.Occurrence += 1;
        }

        private static void LinkAllFeaturesToAllEntities<TClassification, TFeature, TLink>
            (IEnumerable<TClassification> entities, List<TFeature> features)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            foreach (var entity in entities)
            {
                foreach (var feature in features)
                {
                    entity.LinkToFeature<TClassification, TFeature, TLink>(feature);
                }
            }
        }

        private static IFeatureLink FindByFeature<TFeature>
            (this IEnumerable<IFeatureLink> links, TFeature feature)
            where TFeature : IFeature
        {
            return links.SingleOrDefault(l => l.FeatureId.Equals(feature.Id));
        }

        private static TLink CreateNewLink<TClassification, TFeature, TLink>
            (TClassification entity, TFeature feature)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
            where TLink : IFeatureLink, new()
        {
            var link = new TLink
                           {
                               FeatureId = feature.Id,
                               Occurrence = 0
                           };

            entity.AddLink(link);

            return link;
        }
    }
}